# asyncio是Python 3.4版本引入的标准库，直接内置了对异步IO的支持。
# asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用，然后把需要执行的协程扔到EventLoop中执行，就实现了异步IO。
# import asyncio
#
# async def hello():
#     print('hello world')
#     r = await asyncio.sleep(1)
#     print('hello again')
#
# # 获取EventLoop
# loop = asyncio.get_event_loop()
# # 执行coroutine
# loop.run_until_complete(hello())
# loop.close()

# @asyncio.coroutine把一个generator标记为coroutine类型，然后，我们就把这个coroutine扔到EventLoop中执行。
# hello()会首先打印出Hello world!，然后，yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine，所以线程不会等待asyncio.sleep()，而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时，线程就可以从yield from拿到返回值（此处是None），然后接着执行下一行语句。
# 把asyncio.sleep(1)看成是一个耗时1秒的IO操作，在此期间，主线程并未等待，而是去执行EventLoop中其他可以执行的coroutine了，因此可以实现并发执行。

# 我们用Task封装两个coroutine试试：
# import threading
# import asyncio
#
# from mysql.connector import connect
#
#
# async def hello():
#     print('hello world %s' % threading.current_thread())
#     await asyncio.sleep(1)
#     print('hello again %s' % threading.current_thread())
# loop = asyncio.get_event_loop()
# tasks = [hello(), hello()]
# loop.run_until_complete(asyncio.wait(tasks))
# loop.close()

# 由打印的当前线程名称可以看出，两个coroutine是由同一个线程并发执行的。
# 如果把asyncio.sleep()换成真正的IO操作，则多个coroutine就可以由一个线程并发执行。

# 我们用asyncio的异步网络连接来获取sina、sohu和163的网站首页：3个连接由一个线程通过coroutine并发完成。
import asyncio
async def wget(host):
    print('wget %s' % host)
    # 使用asyncio.open_connection异步建立到指定主机的80端口的连接
    # 作用：该方法用于异步建立一个到指定主机和端口的TCP连接。
    # 返回值：返回一个协程对象。这个协程对象代表了连接建立的操作。当这个协程执行完成（即连接建立成功）后，会返回一个包含两个元素的元组(reader, writer)。
    connect = asyncio.open_connection(host, 80)
    # 等待连接建立完成，获取reader和writer对象
    # 作用：使用await关键字等待前面asyncio.open_connection方法返回的协程对象执行完成。
    # 含义：await关键字用于挂起当前协程的执行，直到等待的协程（这里是connect）完成。这样可以让其他协程在当前协程等待期间运行，充分利用单线程的异步特性来实现并发。
    reader, writer = await connect
    # 构造HTTP GET请求头
    header = 'GET / Http/1.0\r\nHost: %s\r\n\r\n' % host
    writer.write(header.encode('utf-8'))
    # 等待数据写入完成
    # 作用：该方法用于异步刷新写入缓冲区，确保所有写入的数据都被发送出去。
    # 含义：在网络编程中，数据写入操作可能不会立即发送到网络上，而是先放入一个缓冲区。writer.drain()方法的作用就是等待缓冲区中的数据被实际发送出去。
    await writer.drain()
    while True:
        # 异步读取一行响应内容
        line = await reader.readline()
        if line == b'\r\n':
            break
        print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
    writer.close()
loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
